﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Expressions;

#endregion

namespace Weazel.Badger.Vhdl.Statements
{
  public class SignalAssignmentStatement : SequentialStatement, IComponent
  {
		/// <summary>
		/// The signal to be assigned by this statement
		/// </summary>
    private Signal signal;
    public Signal Signal
    {
      get
      {
        return signal;
      }

      set
      {
        signal = value;
      }
    }

		/// <summary>
		/// Delay in nanoseonds before assignment
		/// </summary>
    private int waitPeriod;
    public int WaitPeriod
    {
      get
      {
        return waitPeriod;
      }

      set
      {
        waitPeriod = value;
      }
    }

		/// <summary>
		/// Expression to assign to signal
		/// </summary>
    private Expression expression;
    public Expression Expression
    {
      get
      {
        return expression;
      }

      set
      {
        expression = value;        
      }
    }

		/// <summary>
		/// True if the assigned signal should be indexed.
		/// If false the rangeStart and rangeEnd indexs will 
		/// be ignored
		/// </summary>
		private bool usingRange = false;
		public bool UsingRange
		{
			get { return usingRange; }
			set { usingRange = value; }
		}

		private int rangeStart;
		public int RangeStart
		{
			get { return rangeStart; }
			set { rangeStart = value; }
		}

		private int rangeEnd;
		public int RangeEnd
		{
			get { return rangeEnd; }
			set { rangeEnd = value; }
		}

		private bool downto;
		public bool Downto
		{
			get { return downto; }
			set { downto = value; }
		}

		public override void Resolve(Entity entity, Process process, Statements.StatementCollection statements)
    {
      TypeSet signalTypes = signal.GetPossibleTypes();
      TypeSet exprTypes = expression.GetPossibleTypes();

      TypeSet intersection = signalTypes.Intersection(exprTypes);

			System.Diagnostics.Debug.Assert(signalTypes.Count == 1);

			if (intersection.Count == 0)
			{
				// perhaps we can convert ..
				TypeConversionResult result = 
					TypeConverter.GetConversion(exprTypes, signalTypes[0], expression.GezelType, signal.GezelType, false);

        if (result == null)
        {
          string message = 
            string.Format("Signal assignment expression: no conversion possible for {0}: {1} to {2}: {3}.", expression.ToString(), exprTypes, signal.ToString(), signalTypes);
          throw new Exception(message);
        }

				expression = ConversionBuilder.ApplyConversion(result, statements, entity, process, expression);
			}
			else if (intersection.Count == 1)
			{
				signal.SetActualType(entity, process, statements, intersection[0]);
				expression.SetActualType(entity, process, statements, intersection[0]);
			}
			else
			{
				throw new Exception();
			}
    }

    public override void Verify(ref List<VerifyMessage> messages)
    {
      expression.VerifyType(ref messages);
      signal.VerifyType(ref messages);
    }

    public SignalAssignmentStatement(Signal signal, Expression expression) : 
      this(signal, expression, 0)
    {
    }

		public SignalAssignmentStatement(Signal signal, Expression expression, int rangeStart, int rangeEnd, bool downto)
			: this(signal, expression, 0)
		{
			this.usingRange = true;
			this.rangeEnd = rangeEnd;
			this.rangeStart = rangeStart;
			this.downto = downto;
		}

		/// <summary>
		/// Create a delayed assignment signal assignment statement
		/// </summary>
		/// <param name="signal">Signal to assign</param>
		/// <param name="expression">Expression to assign</param>
		/// <param name="waitPeriod">Delay in nanoseconds</param>
    public SignalAssignmentStatement(Signal signal, Expression expression, int waitPeriod)
    {
			System.Diagnostics.Debug.Assert(signal != null);
			System.Diagnostics.Debug.Assert(expression != null);

      this.waitPeriod = waitPeriod;

      if (signal is RegisterReadSignal)
        this.signal = ((RegisterReadSignal)signal).Register.WriteSignal;
      else
        this.signal = signal;

      this.expression = expression;

      signal.AddAssignmentStatement(this);
    }

    /// <summary>
    /// Special constructor for doing 'register update'
    /// assignment.
    /// </summary>
    /// <param name="register">Register to be assigned</param>
		/// <param name="expression">Expression to assign to register</param>
    public SignalAssignmentStatement(VhdlRegister register, Expression expression)
    {
      this.Signal = register.ReadSignal;
      this.Expression = expression;
    }

		private string getRangeModifier()
		{
			if(!usingRange) // no range modifier
				return "";

			if (rangeEnd == rangeStart)
				return "(" + rangeEnd.ToString() + ")";
			else 
				return downto ?
					string.Format("({0} downto {1})", rangeEnd, rangeStart)
				:	string.Format("({0} to {1})", rangeStart, rangeEnd);
		}

		/// <summary>
		/// Write this statement to a VHDL file stream writer
		/// </summary>
		/// <param name="writer"></param>
    public override void Write(System.CodeDom.Compiler.IndentedTextWriter writer)
    {
			string rangeModifier = getRangeModifier();
		
      if(waitPeriod == 0)
        writer.WriteLine(String.Format("{0}{1} <= {2};", signal.Name, rangeModifier, expression.ToString()));     
      else
        writer.WriteLine(String.Format("{0}{1} <= {2} after {3} ns;", signal.Name, rangeModifier, expression.ToString(), waitPeriod));
    }

    public override string ToString()
    {
			string rangeModifier = getRangeModifier();

      return String.Format("{0}{1} <= {2};", signal.Name, rangeModifier, expression.ToString());
    }
  }
}
